package com.appspot.moviesessions.infrastructure.persistence; 
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URL;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import com.appspot.moviesessions.infrastructure.scraper.URLFetchService;
import com.appspot.moviesessions.model.Movie.Fields;
import com.appspot.moviesessions.model.MovieInformationFoundEvent;
import com.appspot.moviesessions.model.MoviePosterFoundEvent;
import com.google.appengine.api.blobstore.BlobKey;
import com.google.appengine.api.blobstore.BlobstoreService;
import com.google.appengine.api.images.ImagesService;
import com.google.appengine.api.urlfetch.FetchOptions;
import com.google.appengine.api.urlfetch.HTTPHeader;
import com.google.appengine.api.urlfetch.HTTPMethod;
import com.google.appengine.api.urlfetch.HTTPRequest;
import com.google.appengine.api.utils.SystemProperty;
import com.google.appengine.api.utils.SystemProperty.Environment;
import com.google.appengine.tools.admin.Application;

@Controller
public class ImageRepositoryImpl implements
        ApplicationListener<MoviePosterFoundEvent> {

	private static Log LOG = LogFactory.getLog(ImageRepositoryImpl.class);

	private URLFetchService urlFetchService;
	private BlobstoreService blobStoreService;
	private ImagesService imageService;
	private ApplicationEventPublisher eventPublisher;

	@Autowired
	public ImageRepositoryImpl(URLFetchService urlFetchService,
	        BlobstoreService blobStoreService, ImagesService imageService,ApplicationEventPublisher publisher) {
		this.urlFetchService = urlFetchService;
		this.blobStoreService = blobStoreService;
		this.imageService = imageService;
		this.eventPublisher = publisher;
	}

	public void onApplicationEvent(MoviePosterFoundEvent event) {
		save(event.getName(), event.getImageData());
	};

	public void save(String movieName, byte[] imageData) {
		try {
			String urlStr = blobStoreService.createUploadUrl("/app/blobimage/callback");
			
			if(SystemProperty.environment.value() == SystemProperty.Environment.Value.Production){
				/*for some reason the blobstore creates relative urls in dev not in prod*/
				urlStr = blobStoreService.createUploadUrl("/blobimage/callback");
			}
			
			HTTPRequest req = new HTTPRequest(new URL(urlStr), HTTPMethod.POST,
			        FetchOptions.Builder.withDeadline(10.0));

			String boundary = makeBoundary();

			req.setHeader(new HTTPHeader("Content-Type",
			        "multipart/form-data; boundary=" + boundary));

			ByteArrayOutputStream baos = new ByteArrayOutputStream();

			write(baos, "--" + boundary + "\r\n");
			writeParameter(baos, "name", movieName);
			write(baos, "--" + boundary + "\r\n");
			writeImage(baos, "save", imageData);
			write(baos, "--" + boundary + "--\r\n");

			req.setPayload(baos.toByteArray());

			urlFetchService.fetch(req);
		} catch (IOException e) {
			// Need a better way of handling Timeout exceptions here - 10 second
			// deadline
			LOG.error("Possible timeout?", e);
		}
	}

	private static Random random = new Random();

	private static String randomString() {
		return Long.toString(random.nextLong(), 36);
	}

	private String makeBoundary() {
		return "---------------------------" + randomString() + randomString()
		        + randomString();
	}

	private void write(OutputStream os, String s) throws IOException {
		os.write(s.getBytes());
	}

	private void writeParameter(OutputStream os, String name, String value)
	        throws IOException {
		write(os, "Content-Disposition: form-data; name=\"" + name
		        + "\"\r\n\r\n" + value + "\r\n");
	}

	private void writeImage(OutputStream os, String name, byte[] bs)
	        throws IOException {
		write(os, "Content-Disposition: form-data; name=\"" + name
		        + "\"; filename=\"image.jpg\"\r\n");
		write(os, "Content-Type: image/jpeg\r\n\r\n");
		os.write(bs);
		write(os, "\r\n");
	}

	@RequestMapping(value="/blobimage/callback")
	public String blobUploadCallback(HttpServletRequest request) {
		Map<String, List<BlobKey>> uploads = blobStoreService.getUploads(request);
		if(uploads.isEmpty()){
			throw new RuntimeException("no uploads in blobstore callback");
		}
		List<BlobKey> keys = uploads.entrySet().iterator().next().getValue();
		BlobKey blobKey = keys.get(0);
		String servingUrl = imageService.getServingUrl(blobKey);
		Map<Fields, Object> updates = new HashMap<Fields,Object>();
		updates.put(Fields.POSTER, servingUrl);
		updates.put(Fields.POSTERKEY, blobKey.getKeyString());
		MovieInformationFoundEvent event=  new MovieInformationFoundEvent(this, request.getParameter("name"), updates);
		eventPublisher.publishEvent(event);
		return "ok";
    }
}
